blob: 5ab38b8b3e00e88ee5668fddde5317917cb2958a [file] [log] [blame]
Deniz Türkoglueb78b602012-05-07 14:02:36 -07001Gerrit Code Review - Plugin Development
2=======================================
3
Edwin Kempinaf275322012-07-16 11:04:01 +02004The Gerrit server functionality can be extended by installing plugins.
5This page describes how plugins for Gerrit can be developed.
6
7Depending on how tightly the extension code is coupled with the Gerrit
8server code, there is a distinction between `plugins` and `extensions`.
9
Edwin Kempinf5a77332012-07-18 11:17:53 +020010[[plugin]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020011A `plugin` in Gerrit is tightly coupled code that runs in the same
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070012JVM as Gerrit. It has full access to all server internals. Plugins
13are tightly coupled to a specific major.minor server version and
14may require source code changes to compile against a different
15server version.
16
Edwin Kempinf5a77332012-07-18 11:17:53 +020017[[extension]]
Edwin Kempin948de0f2012-07-16 10:34:35 +020018An `extension` in Gerrit runs inside of the same JVM as Gerrit
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070019in the same way as a plugin, but has limited visibility to the
Edwin Kempinfd19bfb2012-07-16 10:44:17 +020020server's internals. The limited visibility reduces the extension's
21dependencies, enabling it to be compatible across a wider range
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070022of server versions.
23
24Most of this documentation refers to either type as a plugin.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070025
Edwin Kempinf878c4b2012-07-18 09:34:25 +020026[[getting-started]]
27Getting started
28---------------
Deniz Türkoglueb78b602012-05-07 14:02:36 -070029
Edwin Kempinf878c4b2012-07-18 09:34:25 +020030To get started with the development of a plugin there are two
31recommended ways:
Dave Borowitz5cc8f662012-05-21 09:51:36 -070032
Edwin Kempinf878c4b2012-07-18 09:34:25 +020033. use the Gerrit Plugin Maven archetype to create a new plugin project:
34+
35With the Gerrit Plugin Maven archetype you can create a skeleton for a
36plugin project.
37+
38----
39mvn archetype:generate -DarchetypeGroupId=com.google.gerrit \
40 -DarchetypeArtifactId=gerrit-plugin-archetype \
41 -DarchetypeVersion=2.5-SNAPSHOT \
42 -DgroupId=com.google.gerrit \
43 -DartifactId=testPlugin
44----
45+
46Maven will ask for additional properties and then create the plugin in
47the current directory. To change the default property values answer 'n'
48when Maven asks to confirm the properties configuration. It will then
49ask again for all properties including those with predefined default
50values.
51
David Pursehouse2cf0cb52013-08-27 16:09:53 +090052. clone the sample plugin:
Edwin Kempinf878c4b2012-07-18 09:34:25 +020053+
David Pursehouse2cf0cb52013-08-27 16:09:53 +090054This is a project that demonstrates the various features of the
55plugin API. It can be taken as an example to develop an own plugin.
Edwin Kempinf878c4b2012-07-18 09:34:25 +020056+
Dave Borowitz5cc8f662012-05-21 09:51:36 -070057----
David Pursehouse2cf0cb52013-08-27 16:09:53 +090058$ git clone https://gerrit.googlesource.com/plugins/cookbook-plugin
Dave Borowitz5cc8f662012-05-21 09:51:36 -070059----
Edwin Kempinf878c4b2012-07-18 09:34:25 +020060+
61When starting from this example one should take care to adapt the
62`Gerrit-ApiVersion` in the `pom.xml` to the version of Gerrit for which
63the plugin is developed. If the plugin is developed for a released
64Gerrit version (no `SNAPSHOT` version) then the URL for the
65`gerrit-api-repository` in the `pom.xml` needs to be changed to
Shawn Pearced5005002013-06-21 11:01:45 -070066`https://gerrit-api.storage.googleapis.com/release/`.
Dave Borowitz5cc8f662012-05-21 09:51:36 -070067
Edwin Kempinf878c4b2012-07-18 09:34:25 +020068[[API]]
69API
70---
71
72There are two different API formats offered against which plugins can
73be developed:
Deniz Türkoglueb78b602012-05-07 14:02:36 -070074
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070075gerrit-extension-api.jar::
76 A stable but thin interface. Suitable for extensions that need
77 to be notified of events, but do not require tight coupling to
78 the internals of Gerrit. Extensions built against this API can
79 expect to be binary compatible across a wide range of server
80 versions.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070081
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070082gerrit-plugin-api.jar::
83 The complete internals of the Gerrit server, permitting a
84 plugin to tightly couple itself and provide additional
85 functionality that is not possible as an extension. Plugins
86 built against this API are expected to break at the source
87 code level between every major.minor Gerrit release. A plugin
88 that compiles against 2.5 will probably need source code level
89 changes to work with 2.6, 2.7, and so on.
Deniz Türkoglueb78b602012-05-07 14:02:36 -070090
91Manifest
92--------
93
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070094Plugins may provide optional description information with standard
95manifest fields:
Nasser Grainawie033b262012-05-09 17:54:21 -070096
Shawn O. Pearceda4919a2012-05-10 16:54:28 -070097====
98 Implementation-Title: Example plugin showing examples
99 Implementation-Version: 1.0
100 Implementation-Vendor: Example, Inc.
101 Implementation-URL: http://example.com/opensource/plugin-foo/
102====
Nasser Grainawie033b262012-05-09 17:54:21 -0700103
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700104ApiType
105~~~~~~~
Nasser Grainawie033b262012-05-09 17:54:21 -0700106
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700107Plugins using the tightly coupled `gerrit-plugin-api.jar` must
108declare this API dependency in the manifest to gain access to server
Edwin Kempin948de0f2012-07-16 10:34:35 +0200109internals. If no `Gerrit-ApiType` is specified the stable `extension`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700110API will be assumed. This may cause ClassNotFoundExceptions when
111loading a plugin that needs the plugin API.
Nasser Grainawie033b262012-05-09 17:54:21 -0700112
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700113====
114 Gerrit-ApiType: plugin
115====
116
117Explicit Registration
118~~~~~~~~~~~~~~~~~~~~~
119
120Plugins that use explicit Guice registration must name the Guice
121modules in the manifest. Up to three modules can be named in the
Edwin Kempin948de0f2012-07-16 10:34:35 +0200122manifest. `Gerrit-Module` supplies bindings to the core server;
123`Gerrit-SshModule` supplies SSH commands to the SSH server (if
124enabled); `Gerrit-HttpModule` supplies servlets and filters to the HTTP
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700125server (if enabled). If no modules are named automatic registration
126will be performed by scanning all classes in the plugin JAR for
127`@Listen` and `@Export("")` annotations.
128
129====
130 Gerrit-Module: tld.example.project.CoreModuleClassName
131 Gerrit-SshModule: tld.example.project.SshModuleClassName
132 Gerrit-HttpModule: tld.example.project.HttpModuleClassName
133====
134
Edwin Kempinf7295742012-07-16 15:03:46 +0200135[[reload_method]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700136Reload Method
137~~~~~~~~~~~~~
138
139If a plugin holds an exclusive resource that must be released before
140loading the plugin again (for example listening on a network port or
Edwin Kempin948de0f2012-07-16 10:34:35 +0200141acquiring a file lock) the manifest must declare `Gerrit-ReloadMode`
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700142to be `restart`. Otherwise the preferred method of `reload` will
143be used, as it enables the server to hot-patch an updated plugin
144with no down time.
145
146====
147 Gerrit-ReloadMode: restart
148====
149
150In either mode ('restart' or 'reload') any plugin or extension can
151be updated without restarting the Gerrit server. The difference is
152how Gerrit handles the upgrade:
153
154restart::
155 The old plugin is completely stopped. All registrations of SSH
156 commands and HTTP servlets are removed. All registrations of any
157 extension points are removed. All registered LifecycleListeners
158 have their `stop()` method invoked in reverse order. The new
159 plugin is started, and registrations are made from the new
160 plugin. There is a brief window where neither the old nor the
161 new plugin is connected to the server. This means SSH commands
162 and HTTP servlets will return not found errors, and the plugin
163 will not be notified of events that occurred during the restart.
164
165reload::
166 The new plugin is started. Its LifecycleListeners are permitted
167 to perform their `start()` methods. All SSH and HTTP registrations
168 are atomically swapped out from the old plugin to the new plugin,
169 ensuring the server never returns a not found error. All extension
170 point listeners are atomically swapped out from the old plugin to
171 the new plugin, ensuring no events are missed (however some events
172 may still route to the old plugin if the swap wasn't complete yet).
173 The old plugin is stopped.
174
Edwin Kempinf7295742012-07-16 15:03:46 +0200175To reload/restart a plugin the link:cmd-plugin-reload.html[plugin reload]
176command can be used.
177
Luca Milanesio737285d2012-09-25 14:26:43 +0100178[[init_step]]
179Init step
180~~~~~~~~~
181
182Plugins can contribute their own "init step" during the Gerrit init
183wizard. This is useful for guiding the Gerrit administrator through
184the settings needed by the plugin to work propertly.
185
186For instance plugins to integrate Jira issues to Gerrit changes may
187contribute their own "init step" to allow configuring the Jira URL,
188credentials and possibly verify connectivity to validate them.
189
190====
191 Gerrit-InitStep: tld.example.project.MyInitStep
192====
193
194MyInitStep needs to follow the standard Gerrit InitStep syntax
David Pursehouse92463562013-06-24 10:16:28 +0900195and behavior: writing to the console using the injected ConsoleUI
Luca Milanesio737285d2012-09-25 14:26:43 +0100196and accessing / changing configuration settings using Section.Factory.
197
198In addition to the standard Gerrit init injections, plugins receive
199the @PluginName String injection containing their own plugin name.
200
201Bear in mind that the Plugin's InitStep class will be loaded but
202the standard Gerrit runtime environment is not available and the plugin's
203own Guice modules were not initialized.
204This means the InitStep for a plugin is not executed in the same way that
205the plugin executes within the server, and may mean a plugin author cannot
206trivially reuse runtime code during init.
207
208For instance a plugin that wants to verify connectivity may need to statically
209call the constructor of their connection class, passing in values obtained
210from the Section.Factory rather than from an injected Config object.
211
212Plugins InitStep are executing during the "Gerrit Plugin init" phase, after
213the extraction of the plugins embedded in Gerrit.war into $GERRIT_SITE/plugins
214and before the DB Schema initialization or upgrade.
215Plugins InitStep cannot refer to Gerrit DB Schema or any other Gerrit runtime
216objects injected at startup.
217
David Pursehouse68153d72013-09-04 10:09:17 +0900218[source,java]
219----
220public class MyInitStep implements InitStep {
221 private final ConsoleUI ui;
222 private final Section.Factory sections;
223 private final String pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100224
David Pursehouse68153d72013-09-04 10:09:17 +0900225 @Inject
226 public GitBlitInitStep(final ConsoleUI ui, Section.Factory sections, @PluginName String pluginName) {
227 this.ui = ui;
228 this.sections = sections;
229 this.pluginName = pluginName;
Luca Milanesio737285d2012-09-25 14:26:43 +0100230 }
David Pursehouse68153d72013-09-04 10:09:17 +0900231
232 @Override
233 public void run() throws Exception {
234 ui.header("\nMy plugin");
235
236 Section mySection = getSection("myplugin", null);
237 mySection.string("Link name", "linkname", "MyLink");
238 }
239}
240----
Luca Milanesio737285d2012-09-25 14:26:43 +0100241
Edwin Kempinf5a77332012-07-18 11:17:53 +0200242[[classpath]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700243Classpath
244---------
245
246Each plugin is loaded into its own ClassLoader, isolating plugins
247from each other. A plugin or extension inherits the Java runtime
248and the Gerrit API chosen by `Gerrit-ApiType` (extension or plugin)
249from the hosting server.
250
251Plugins are loaded from a single JAR file. If a plugin needs
252additional libraries, it must include those dependencies within
253its own JAR. Plugins built using Maven may be able to use the
254link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
255to package additional dependencies. Relocating (or renaming) classes
256should not be necessary due to the ClassLoader isolation.
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700257
Edwin Kempin98202662013-09-18 16:03:03 +0200258[[events]]
259Listening to Events
260-------------------
261
262Certain operations in Gerrit trigger events. Plugins may receive
263notifications of these events by implementing the corresponding
264listeners.
265
266* `com.google.gerrit.extensions.events.LifecycleListener`:
267+
268Gerrit server startup and shutdown
269
270* `com.google.gerrit.extensions.events.NewProjectCreatedListener`:
271+
272Project creation
273
274* `com.google.gerrit.extensions.events.ProjectDeletedListener`:
275+
276Project deletion
277
Edwin Kempinf5a77332012-07-18 11:17:53 +0200278[[ssh]]
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700279SSH Commands
280------------
281
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700282Plugins may provide commands that can be accessed through the SSH
283interface (extensions do not have this option).
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700284
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700285Command implementations must extend the base class SshCommand:
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700286
David Pursehouse68153d72013-09-04 10:09:17 +0900287[source,java]
288----
289import com.google.gerrit.sshd.SshCommand;
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700290
David Pursehouse68153d72013-09-04 10:09:17 +0900291class PrintHello extends SshCommand {
292 protected abstract void run() {
293 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700294 }
David Pursehouse68153d72013-09-04 10:09:17 +0900295}
296----
Nasser Grainawie033b262012-05-09 17:54:21 -0700297
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700298If no Guice modules are declared in the manifest, SSH commands may
Edwin Kempin948de0f2012-07-16 10:34:35 +0200299use auto-registration by providing an `@Export` annotation:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700300
David Pursehouse68153d72013-09-04 10:09:17 +0900301[source,java]
302----
303import com.google.gerrit.extensions.annotations.Export;
304import com.google.gerrit.sshd.SshCommand;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700305
David Pursehouse68153d72013-09-04 10:09:17 +0900306@Export("print")
307class PrintHello extends SshCommand {
308 protected abstract void run() {
309 stdout.print("Hello\n");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700310 }
David Pursehouse68153d72013-09-04 10:09:17 +0900311}
312----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700313
314If explicit registration is being used, a Guice module must be
315supplied to register the SSH command and declared in the manifest
316with the `Gerrit-SshModule` attribute:
317
David Pursehouse68153d72013-09-04 10:09:17 +0900318[source,java]
319----
320import com.google.gerrit.sshd.PluginCommandModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700321
David Pursehouse68153d72013-09-04 10:09:17 +0900322class MyCommands extends PluginCommandModule {
323 protected void configureCommands() {
324 command("print").to(PrintHello.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700325 }
David Pursehouse68153d72013-09-04 10:09:17 +0900326}
327----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700328
329For a plugin installed as name `helloworld`, the command implemented
330by PrintHello class will be available to users as:
331
332----
Keunhong Parka09a6f12012-07-10 14:45:02 -0600333$ ssh -p 29418 review.example.com helloworld print
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700334----
335
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200336[[configuration]]
337Configuration
338-------------
339
340In Gerrit, global configuration is stored in the `gerrit.config` file.
341If a plugin needs global configuration, this configuration should be
342stored in a `plugin` subsection in the `gerrit.config` file.
343
344To avoid conflicts with other plugins, it is recommended that plugins
345only use the `plugin` subsection with their own name. For example the
346`helloworld` plugin should store its configuration in the
347`plugin.helloworld` subsection:
348
349----
350[plugin "helloworld"]
351 language = Latin
352----
353
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200354Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200355plugin can easily access its configuration and there is no need for a
356plugin to parse the `gerrit.config` file on its own:
357
358[source,java]
359----
360 @Inject
Sasa Zivkovacdf5332013-09-20 14:05:15 +0200361 private com.google.gerrit.server.config.PluginConfigFactory cfg;
Edwin Kempinf7bfff82013-09-17 13:34:20 +0200362
363 ...
364
365 String language = cfg.get("helloworld")
366 .getString("language", "English");
367----
368
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200369[[project-specific-configuration]]
370Project Specific Configuration
371------------------------------
372
373In Gerrit, project specific configuration is stored in the project's
374`project.config` file on the `refs/meta/config` branch. If a plugin
375needs configuration on project level (e.g. to enable its functionality
376only for certain projects), this configuration should be stored in a
377`plugin` subsection in the project's `project.config` file.
378
379To avoid conflicts with other plugins, it is recommended that plugins
380only use the `plugin` subsection with their own name. For example the
381`helloworld` plugin should store its configuration in the
382`plugin.helloworld` subsection:
383
384----
385 [plugin "helloworld"]
386 enabled = true
387----
388
389Via the `com.google.gerrit.server.config.PluginConfigFactory` class a
390plugin can easily access its project specific configuration and there
391is no need for a plugin to parse the `project.config` file on its own:
392
393[source,java]
394----
395 @Inject
396 private com.google.gerrit.server.config.PluginConfigFactory cfg;
397
398 ...
399
400 boolean enabled = cfg.get(project, "helloworld")
401 .getBoolean("enabled", false);
402----
403
Edwin Kempinca7ad8e2013-09-16 16:43:05 +0200404It is also possible to get missing configuration parameters inherited
405from the parent projects:
406
407[source,java]
408----
409 @Inject
410 private com.google.gerrit.server.config.PluginConfigFactory cfg;
411
412 ...
413
414 boolean enabled = cfg.getWithInheritance(project, "helloworld")
415 .getBoolean("enabled", false);
416----
417
Edwin Kempin7b2f4cc2013-08-26 15:44:19 +0200418Project owners can edit the project configuration by fetching the
419`refs/meta/config` branch, editing the `project.config` file and
420pushing the commit back.
421
David Ostrovsky7066cc02013-06-15 14:46:23 +0200422[[capabilities]]
423Plugin Owned Capabilities
424-------------------------
425
426Plugins may provide their own capabilities and restrict usage of SSH
427commands to the users who are granted those capabilities.
428
429Plugins define the capabilities by overriding the `CapabilityDefinition`
430abstract class:
431
David Pursehouse68153d72013-09-04 10:09:17 +0900432[source,java]
433----
434public class PrintHelloCapability extends CapabilityDefinition {
435 @Override
436 public String getDescription() {
437 return "Print Hello";
David Ostrovsky7066cc02013-06-15 14:46:23 +0200438 }
David Pursehouse68153d72013-09-04 10:09:17 +0900439}
440----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200441
David Ostrovskyf86bae52013-09-01 09:10:39 +0200442If no Guice modules are declared in the manifest, UI actions may
David Ostrovsky7066cc02013-06-15 14:46:23 +0200443use auto-registration by providing an `@Export` annotation:
444
David Pursehouse68153d72013-09-04 10:09:17 +0900445[source,java]
446----
447@Export("printHello")
448public class PrintHelloCapability extends CapabilityDefinition {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200449 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900450}
451----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200452
453Otherwise the capability must be bound in a plugin module:
454
David Pursehouse68153d72013-09-04 10:09:17 +0900455[source,java]
456----
457public class HelloWorldModule extends AbstractModule {
458 @Override
459 protected void configure() {
460 bind(CapabilityDefinition.class)
461 .annotatedWith(Exports.named("printHello"))
462 .to(PrintHelloCapability.class);
David Ostrovsky7066cc02013-06-15 14:46:23 +0200463 }
David Pursehouse68153d72013-09-04 10:09:17 +0900464}
465----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200466
467With a plugin-owned capability defined in this way, it is possible to restrict
David Ostrovskyf86bae52013-09-01 09:10:39 +0200468usage of an SSH command or `UiAction` to members of the group that were granted
David Ostrovsky7066cc02013-06-15 14:46:23 +0200469this capability in the usual way, using the `RequiresCapability` annotation:
470
David Pursehouse68153d72013-09-04 10:09:17 +0900471[source,java]
472----
473@RequiresCapability("printHello")
474@CommandMetaData(name="print", description="Print greeting in different languages")
475public final class PrintHelloWorldCommand extends SshCommand {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200476 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900477}
478----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200479
David Ostrovskyf86bae52013-09-01 09:10:39 +0200480Or with `UiAction`:
David Ostrovsky7066cc02013-06-15 14:46:23 +0200481
David Pursehouse68153d72013-09-04 10:09:17 +0900482[source,java]
483----
484@RequiresCapability("printHello")
485public class SayHelloAction extends UiAction<RevisionResource>
486 implements RestModifyView<RevisionResource, SayHelloAction.Input> {
David Ostrovsky7066cc02013-06-15 14:46:23 +0200487 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900488}
489----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200490
491Capability scope was introduced to differentiate between plugin-owned
David Pursehousebf053342013-09-05 14:55:29 +0900492capabilities and core capabilities. Per default the scope of the
493`@RequiresCapability` annotation is `CapabilityScope.CONTEXT`, that means:
494
David Ostrovsky7066cc02013-06-15 14:46:23 +0200495* when `@RequiresCapability` is used within a plugin the scope of the
496capability is assumed to be that plugin.
David Pursehousebf053342013-09-05 14:55:29 +0900497
David Ostrovsky7066cc02013-06-15 14:46:23 +0200498* If `@RequiresCapability` is used within the core Gerrit Code Review server
499(and thus is outside of a plugin) the scope is the core server and will use
500the `GlobalCapability` known to Gerrit Code Review server.
501
502If a plugin needs to use a core capability name (e.g. "administrateServer")
503this can be specified by setting `scope = CapabilityScope.CORE`:
504
David Pursehouse68153d72013-09-04 10:09:17 +0900505[source,java]
506----
507@RequiresCapability(value = "administrateServer", scope =
508 CapabilityScope.CORE)
David Ostrovsky7066cc02013-06-15 14:46:23 +0200509 ...
David Pursehouse68153d72013-09-04 10:09:17 +0900510----
David Ostrovsky7066cc02013-06-15 14:46:23 +0200511
David Ostrovskyf86bae52013-09-01 09:10:39 +0200512[[ui_extension]]
513UI Extension
514------------
515
516Plugins can contribute their own UI commands on core Gerrit pages.
517This is useful for workflow customization or exposing plugin functionality
518through the UI in addition to SSH commands and the REST API.
519
520For instance a plugin to integrate Jira with Gerrit changes may contribute its
521own "File bug" button to allow filing a bug from the change page or plugins to
522integrate continuous integration systems may contribute a "Schedule" button to
523allow a CI build to be scheduled manually from the patch set panel.
524
525Two different places on core Gerrit pages are currently supported:
526
527* Change screen
528* Project info screen
529
530Plugins contribute UI actions by implementing the `UiAction` interface:
531
David Pursehouse68153d72013-09-04 10:09:17 +0900532[source,java]
533----
534@RequiresCapability("printHello")
535class HelloWorldAction implements UiAction<RevisionResource>,
536 RestModifyView<RevisionResource, HelloWorldAction.Input> {
537 static class Input {
538 boolean french;
539 String message;
David Ostrovskyf86bae52013-09-01 09:10:39 +0200540 }
David Pursehouse68153d72013-09-04 10:09:17 +0900541
542 private Provider<CurrentUser> user;
543
544 @Inject
545 HelloWorldAction(Provider<CurrentUser> user) {
546 this.user = user;
547 }
548
549 @Override
550 public String apply(RevisionResource rev, Input input) {
551 final String greeting = input.french
552 ? "Bonjour"
553 : "Hello";
554 return String.format("%s %s from change %s, patch set %d!",
555 greeting,
556 Strings.isNullOrEmpty(input.message)
557 ? Objects.firstNonNull(user.get().getUserName(), "world")
558 : input.message,
559 rev.getChange().getId().toString(),
560 rev.getPatchSet().getPatchSetId());
561 }
562
563 @Override
564 public Description getDescription(
565 RevisionResource resource) {
566 return new Description()
567 .setLabel("Say hello")
568 .setTitle("Say hello in different languages");
569 }
570}
571----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200572
573`UiAction` must be bound in a plugin module:
574
David Pursehouse68153d72013-09-04 10:09:17 +0900575[source,java]
576----
577public class Module extends AbstractModule {
578 @Override
579 protected void configure() {
580 install(new RestApiModule() {
581 @Override
582 protected void configure() {
583 post(REVISION_KIND, "say-hello")
584 .to(HelloWorldAction.class);
585 }
586 });
David Ostrovskyf86bae52013-09-01 09:10:39 +0200587 }
David Pursehouse68153d72013-09-04 10:09:17 +0900588}
589----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200590
591The module above must be declared in pom.xml for Maven driven plugins:
592
David Pursehouse68153d72013-09-04 10:09:17 +0900593[source,xml]
594----
595<manifestEntries>
596 <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
597</manifestEntries>
598----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200599
600or in the BUCK configuration file for Buck driven plugins:
601
David Pursehouse68153d72013-09-04 10:09:17 +0900602[source,python]
603----
604manifest_entries = [
605 'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
606]
607----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200608
609In some use cases more user input must be gathered, for that `UiAction` can be
610combined with the JavaScript API. This would display a small popup near the
611activation button to gather additional input from the user. The JS file is
612typically put in the `static` folder within the plugin's directory:
613
David Pursehouse68153d72013-09-04 10:09:17 +0900614[source,javascript]
615----
616Gerrit.install(function(self) {
617 function onSayHello(c) {
618 var f = c.textfield();
619 var t = c.checkbox();
620 var b = c.button('Say hello', {onclick: function(){
621 c.call(
622 {message: f.value, french: t.checked},
623 function(r) {
624 c.hide();
625 window.alert(r);
626 c.refresh();
627 });
628 }});
629 c.popup(c.div(
630 c.prependLabel('Greeting message', f),
631 c.br(),
632 c.label(t, 'french'),
633 c.br(),
634 b));
635 f.focus();
636 }
637 self.onAction('revision', 'say-hello', onSayHello);
638});
639----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200640
641The JS module must be exposed as a `WebUiPlugin` and bound as
642an HTTP Module:
643
David Pursehouse68153d72013-09-04 10:09:17 +0900644[source,java]
645----
646public class HttpModule extends HttpPluginModule {
647 @Override
648 protected void configureServlets() {
649 DynamicSet.bind(binder(), WebUiPlugin.class)
650 .toInstance(new JavaScriptPlugin("hello.js"));
David Ostrovskyf86bae52013-09-01 09:10:39 +0200651 }
David Pursehouse68153d72013-09-04 10:09:17 +0900652}
653----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200654
655The HTTP module above must be declared in pom.xml for Maven driven plugins:
656
David Pursehouse68153d72013-09-04 10:09:17 +0900657[source,xml]
658----
659<manifestEntries>
660 <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
661</manifestEntries>
662----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200663
664or in the BUCK configuration file for Buck driven plugins
665
David Pursehouse68153d72013-09-04 10:09:17 +0900666[source,python]
667----
668manifest_entries = [
669 'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
670]
671----
David Ostrovskyf86bae52013-09-01 09:10:39 +0200672
673If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
674capability check is done during the `UiAction` gathering, so the plugin author
675doesn't have to set `UiAction.Description.setVisible()` explicitly in this
676case.
677
678The following prerequisities must be met, to satisfy the capability check:
679
680* user is authenticated
681* user is a member of the Administrators group, or
682* user is a member of a group which has the required capability
683
684The `apply` method is called when the button is clicked. If `UiAction` is
685combined with JavaScript API (its own JavaScript function is provided),
686then a popup dialog is normally opened to gather additional user input.
687A new button is placed on the popup dialog to actually send the request.
688
689Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
690can be accessed from any REST client, i. e.:
691
692====
693 curl -X POST -H "Content-Type: application/json" \
694 -d '{message: "François", french: true}' \
695 --digest --user joe:secret \
696 http://host:port/a/changes/1/revisions/1/cookbook~say-hello
697 "Bonjour François from change 1, patch set 1!"
698====
699
David Pursehouse42245822013-09-24 09:48:20 +0900700A special case is to bind an endpoint without a view name. This is
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200701particularly useful for DELETE requests:
702
703[source,java]
704----
705public class Module extends AbstractModule {
706 @Override
707 protected void configure() {
708 install(new RestApiModule() {
709 @Override
710 protected void configure() {
711 delete(PROJECT_KIND)
712 .to(DeleteProject.class);
713 }
714 });
715 }
716}
717----
718
David Pursehouse42245822013-09-24 09:48:20 +0900719For a `UiAction` bound this way, a JS API function can be provided.
720
721Currently only one restriction exists: per plugin only one `UiAction`
David Ostrovskyc6d19ed2013-09-20 21:30:18 +0200722can be bound per resource without view name. To define a JS function
723for the `UiAction`, "/" must be used as the name:
724
725[source,javascript]
726----
727Gerrit.install(function(self) {
728 function onDeleteProject(c) {
729 [...]
730 }
731 self.onAction('project', '/', onDeleteProject);
732});
733----
734
Edwin Kempinf5a77332012-07-18 11:17:53 +0200735[[http]]
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700736HTTP Servlets
737-------------
738
739Plugins or extensions may register additional HTTP servlets, and
740wrap them with HTTP filters.
741
742Servlets may use auto-registration to declare the URL they handle:
743
David Pursehouse68153d72013-09-04 10:09:17 +0900744[source,java]
745----
746import com.google.gerrit.extensions.annotations.Export;
747import com.google.inject.Singleton;
748import javax.servlet.http.HttpServlet;
749import javax.servlet.http.HttpServletRequest;
750import javax.servlet.http.HttpServletResponse;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700751
David Pursehouse68153d72013-09-04 10:09:17 +0900752@Export("/print")
753@Singleton
754class HelloServlet extends HttpServlet {
755 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
756 res.setContentType("text/plain");
757 res.setCharacterEncoding("UTF-8");
758 res.getWriter().write("Hello");
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700759 }
David Pursehouse68153d72013-09-04 10:09:17 +0900760}
761----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700762
Edwin Kempin8aa650f2012-07-18 11:25:48 +0200763The auto registration only works for standard servlet mappings like
764`/foo` or `/foo/*`. Regex style bindings must use a Guice ServletModule
765to register the HTTP servlets and declare it explicitly in the manifest
766with the `Gerrit-HttpModule` attribute:
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700767
David Pursehouse68153d72013-09-04 10:09:17 +0900768[source,java]
769----
770import com.google.inject.servlet.ServletModule;
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700771
David Pursehouse68153d72013-09-04 10:09:17 +0900772class MyWebUrls extends ServletModule {
773 protected void configureServlets() {
774 serve("/print").with(HelloServlet.class);
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700775 }
David Pursehouse68153d72013-09-04 10:09:17 +0900776}
777----
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700778
779For a plugin installed as name `helloworld`, the servlet implemented
780by HelloServlet class will be available to users as:
781
782----
783$ curl http://review.example.com/plugins/helloworld/print
784----
Nasser Grainawie033b262012-05-09 17:54:21 -0700785
Edwin Kempinf5a77332012-07-18 11:17:53 +0200786[[data-directory]]
Edwin Kempin41f63912012-07-17 12:33:55 +0200787Data Directory
788--------------
789
790Plugins can request a data directory with a `@PluginData` File
791dependency. A data directory will be created automatically by the
792server in `$site_path/data/$plugin_name` and passed to the plugin.
793
794Plugins can use this to store any data they want.
795
David Pursehouse68153d72013-09-04 10:09:17 +0900796[source,java]
797----
798@Inject
799MyType(@PluginData java.io.File myDir) {
800 new FileInputStream(new File(myDir, "my.config"));
801}
802----
Edwin Kempin41f63912012-07-17 12:33:55 +0200803
Edwin Kempinf5a77332012-07-18 11:17:53 +0200804[[documentation]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700805Documentation
806-------------
807
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700808If a plugin does not register a filter or servlet to handle URLs
809`/Documentation/*` or `/static/*`, the core Gerrit server will
810automatically export these resources over HTTP from the plugin JAR.
811
David Pursehouse6853b5a2013-07-10 11:38:03 +0900812Static resources under the `static/` directory in the JAR will be
Dave Borowitzb893ac82013-03-27 10:03:55 -0400813available as `/plugins/helloworld/static/resource`. This prefix is
814configurable by setting the `Gerrit-HttpStaticPrefix` attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700815
David Pursehouse6853b5a2013-07-10 11:38:03 +0900816Documentation files under the `Documentation/` directory in the JAR
Dave Borowitzb893ac82013-03-27 10:03:55 -0400817will be available as `/plugins/helloworld/Documentation/resource`. This
818prefix is configurable by setting the `Gerrit-HttpDocumentationPrefix`
819attribute.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700820
821Documentation may be written in
822link:http://daringfireball.net/projects/markdown/[Markdown] style
823if the file name ends with `.md`. Gerrit will automatically convert
824Markdown to HTML if accessed with extension `.html`.
Nasser Grainawie033b262012-05-09 17:54:21 -0700825
Edwin Kempinf5a77332012-07-18 11:17:53 +0200826[[macros]]
Edwin Kempinc78777d2012-07-16 15:55:11 +0200827Within the Markdown documentation files macros can be used that allow
828to write documentation with reasonably accurate examples that adjust
829automatically based on the installation.
830
831The following macros are supported:
832
833[width="40%",options="header"]
834|===================================================
835|Macro | Replacement
836|@PLUGIN@ | name of the plugin
837|@URL@ | Gerrit Web URL
838|@SSH_HOST@ | SSH Host
839|@SSH_PORT@ | SSH Port
840|===================================================
841
842The macros will be replaced when the documentation files are rendered
843from Markdown to HTML.
844
845Macros that start with `\` such as `\@KEEP@` will render as `@KEEP@`
846even if there is an expansion for `KEEP` in the future.
847
Edwin Kempinf5a77332012-07-18 11:17:53 +0200848[[auto-index]]
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700849Automatic Index
850~~~~~~~~~~~~~~~
851
852If a plugin does not handle its `/` URL itself, Gerrit will
853redirect clients to the plugin's `/Documentation/index.html`.
854Requests for `/Documentation/` (bare directory) will also redirect
855to `/Documentation/index.html`.
856
857If neither resource `Documentation/index.html` or
858`Documentation/index.md` exists in the plugin JAR, Gerrit will
859automatically generate an index page for the plugin's documentation
860tree by scanning every `*.md` and `*.html` file in the Documentation/
861directory.
862
863For any discovered Markdown (`*.md`) file, Gerrit will parse the
864header of the file and extract the first level one title. This
865title text will be used as display text for a link to the HTML
866version of the page.
867
868For any discovered HTML (`*.html`) file, Gerrit will use the name
869of the file, minus the `*.html` extension, as the link text. Any
870hyphens in the file name will be replaced with spaces.
871
David Pursehouse6853b5a2013-07-10 11:38:03 +0900872If a discovered file is named `about.md` or `about.html`, its
873content will be inserted in an 'About' section at the top of the
874auto-generated index page. If both `about.md` and `about.html`
875exist, only the first discovered file will be used.
876
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700877If a discovered file name beings with `cmd-` it will be clustered
David Pursehouse6853b5a2013-07-10 11:38:03 +0900878into a 'Commands' section of the generated index page.
879
David Pursehousefe529152013-08-14 16:35:06 +0900880If a discovered file name beings with `servlet-` it will be clustered
881into a 'Servlets' section of the generated index page.
882
883If a discovered file name beings with `rest-api-` it will be clustered
884into a 'REST APIs' section of the generated index page.
885
David Pursehouse6853b5a2013-07-10 11:38:03 +0900886All other files are clustered under a 'Documentation' section.
Shawn O. Pearce795167c2012-05-12 11:20:18 -0700887
888Some optional information from the manifest is extracted and
889displayed as part of the index page, if present in the manifest:
890
891[width="40%",options="header"]
892|===================================================
893|Field | Source Attribute
894|Name | Implementation-Title
895|Vendor | Implementation-Vendor
896|Version | Implementation-Version
897|URL | Implementation-URL
898|API Version | Gerrit-ApiVersion
899|===================================================
900
Edwin Kempinf5a77332012-07-18 11:17:53 +0200901[[deployment]]
Nasser Grainawie033b262012-05-09 17:54:21 -0700902Deployment
903----------
904
Edwin Kempinf7295742012-07-16 15:03:46 +0200905Compiled plugins and extensions can be deployed to a running Gerrit
906server using the link:cmd-plugin-install.html[plugin install] command.
Shawn O. Pearceda4919a2012-05-10 16:54:28 -0700907
908Plugins can also be copied directly into the server's
909directory at `$site_path/plugins/$name.jar`. The name of
910the JAR file, minus the `.jar` extension, will be used as the
911plugin name. Unless disabled, servers periodically scan this
912directory for updated plugins. The time can be adjusted by
913link:config-gerrit.html#plugins.checkFrequency[plugins.checkFrequency].
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700914
Edwin Kempinf7295742012-07-16 15:03:46 +0200915For disabling plugins the link:cmd-plugin-remove.html[plugin remove]
916command can be used.
917
Brad Larsond5e87c32012-07-11 12:18:49 -0500918Disabled plugins can be re-enabled using the
919link:cmd-plugin-enable.html[plugin enable] command.
920
David Ostrovskyf86bae52013-09-01 09:10:39 +0200921SEE ALSO
922--------
923
924* link:js-api.html[JavaScript API]
925* link:dev-rest-api.html[REST API Developers' Notes]
926
Deniz Türkoglueb78b602012-05-07 14:02:36 -0700927GERRIT
928------
929Part of link:index.html[Gerrit Code Review]